//////////////////////////////////////////////
// FreeListPager.h
//
//////////////////////////////////////////////

/// Defines / Macros -------------------------

#pragma once

/// Forward decl -----------------------------

namespace nkMemory
{
	template <typename T>
	struct FreeListFreeSpotSize ;
}

/// Includes ---------------------------------

// nkMemory
#include "../../Containers/String.h"

#include "../../Log/LogManager.h"

#include "../../Pointers/UniquePtr.h"

#include "../MemorySpaces/DefaultMemorySpace.h"
#include "../MemorySpaces/DefaultMemorySpaceAllocator.h"

#include "FreeListMemorySpaceWrapper.h"

// Standards
#include <deque>
#include <unordered_map>

/// Internals --------------------------------

namespace nkMemory
{
	// Allocation retournée
	template <typename T>
	struct FreeListAllocation
	{
		// L'allocation demandée
		T _allocation ;

		// Index à garder pour les désallocations
		unsigned long long _index ;
	} ;

	// Allocation gardée en interne
	template <typename T, typename U>
	struct FreeListAllocationData
	{
		FreeListMemorySpaceWrapper<T, U>* _memorySpace ;
		unsigned long long _offset ;
		unsigned long long _size ;
	} ;

	// Info d'itérateurs après l'allocation d'un bloc
	template <typename T, typename U>
	struct SpotAllocation
	{
		typename std::deque<FreeListMemorySpaceWrapper<T, U>>::iterator _pageIterator ;
	} ;
}

/// Class ------------------------------------

namespace nkMemory
{
	// T = return type des allocations
	// U = memory space à utiliser
	template <typename T = char*, typename U = DefaultMemorySpace>
	class FreeListPager final
	{
		public :

			// Constructeur
			FreeListPager (unsigned long long pageSize, UniquePtr<MemorySpaceAllocator<U>> allocator = makeUnique<DefaultMemorySpaceAllocator<U>>()) noexcept
			:	_spaceAllocator (std::move(allocator)),
				_pages (),
				_allocationMap (),
				_pageSize (pageSize),
				_allocationIndex (1)
			{
				// Nothing to do
			}

			FreeListPager (const FreeListPager&) noexcept = delete ;

			FreeListPager (FreeListPager&&) noexcept = default ;

		public :

			// Getters
			unsigned long long getPageSize () const
			{
				return _pageSize ;
			}

			unsigned long long getAllocatedPageCount () const
			{
				return _pages.size() ;
			}

			const FreeListMemorySpaceWrapper<T, U>& getPage (unsigned long long index)
			{
				return _pages[index] ;
			}

		public :

			// Allocations
			FreeListAllocation<T> allocate (unsigned long long size, unsigned long long alignment = 1)
			{
				// Check allocation size
				if (size > _pageSize || alignment > _pageSize)
				{
					String message ;
					message += "Attempted to allocate a chunk of memory of size " ;
					message += std::to_string(size) ;
					message += " and alignment of " ;
					message += std::to_string(alignment) ;
					message += " on a Pager having pages of size " ;
					message += std::to_string(_pageSize) ;
					message += ". Request cannot be handled, returning nullptr." ;

					LogManager::getInstance()->log(message, "FreeListPager") ;

					return FreeListAllocation<T>{T(), 0} ;
				}

				// Check if we have an available memory spot
				typename std::deque<FreeListMemorySpaceWrapper<T, U>>::iterator searchResult = _pages.begin();

				unsigned long long allocationCompatibleOffset = 0 ;

				// Check des zones mémoires avec l'alignement, il nous faut peut être plus gros / zone mémoire ailleurs
				while (searchResult != _pages.end())
				{
					// On calcule l'adresse à prendre
					allocationCompatibleOffset = searchResult->getFittingOffsetFor(size, alignment) ;

					// Check taille, possiblement l'allocation qu'il nous faut
					if (allocationCompatibleOffset != FREE_LIST_INVALID_SIZE)
						break ;

					searchResult++ ;
				}

				// Voyons si on retourne directement ou s'il nous faut une allocation
				if (searchResult == _pages.end())
				{
					// Il nous faut une nouvelle page
					SpotAllocation<T, U> blockInfo = _allocatePage<T, U>() ;

					// On reroute notre itérateur dessus
					searchResult = blockInfo._pageIterator ;
					allocationCompatibleOffset = 0 ;
				}

				// Nous avons notre résultat
				// Les offsets ont été fill lors de la recherche, ou nouvelle page -> 0
				T allocation = searchResult->getOffsetPtr(allocationCompatibleOffset) ;
				// Update du tracking
				_allocationMap.emplace(_allocationIndex, FreeListAllocationData<T, U>{&(*searchResult), allocationCompatibleOffset, size}) ;

				// Update de la page
				searchResult->noticeAllocation(allocationCompatibleOffset, size) ;

				// Nous sommes bons
				return FreeListAllocation<T>{allocation, _allocationIndex++};
			}

			// Free a given allocation
			void free (unsigned long long allocationIndex)
			{
				// On retrouve les infos
				typename std::unordered_map<unsigned long long, FreeListAllocationData<T, U>>::const_iterator searchResult = _allocationMap.find(allocationIndex) ;

				if (searchResult == _allocationMap.end())
				{
					// Demande de free avec un index inconnu, on log ça
					String message ;
					message += "Trying to free an allocation with an unknown index (" ;
					message += std::to_string(allocationIndex) ;
					message += "). Aborting." ;

					LogManager::getInstance()->log(message, "FreeListPager") ;

					return ;
				}

				// Specify to the memory space itself
				searchResult->second._memorySpace->noticeFree(searchResult->second._offset, searchResult->second._size) ;

				// Free de notre map
				_allocationMap.erase(searchResult) ;
			}

		public :

			// Operators
			FreeListPager& operator= (const FreeListPager&) noexcept = delete ;

			FreeListPager& operator= (FreeListPager&&) noexcept = default ;

		private :

			// Functions
			// Page allocation
			template <typename T, typename U>
			SpotAllocation<T, U> _allocatePage ()
			{
				// Emplace devant pour les recherches après
				_pages.emplace_front(_pageSize, _spaceAllocator.get()) ;
				return SpotAllocation<T, U>{_pages.begin()} ;				
			}

		private :

			// Attributes
			UniquePtr<MemorySpaceAllocator<U>> _spaceAllocator ;

			// Ongoing pages
			std::deque<FreeListMemorySpaceWrapper<T, U>> _pages ;

			// Id indexed map of allocations
			std::unordered_map<unsigned long long, FreeListAllocationData<T, U>> _allocationMap ;

			// Page size
			unsigned long long _pageSize ;
			// Current allocation index
			unsigned long long _allocationIndex ;
	} ;
}